Syntax10.Scn.Fnt StampElems Alloc 5 Jul 95 Syntax10b.Scn.Fnt Syntax10i.Scn.Fnt MarkElems Alloc MODULE BalloonElems; (* HM 13 Oct 94 / (*------------------------------------------------------------------------------------ Automatically installs a TextFrame handler that intercepts MM+MR clicks and shows a popup text with an explanation of the word that was clicked at. The module also provides Balloon elements containing a dictionary of words and their explanation. Dictionary = {Word Explanation}. Word = string. Explanation = . ------------------------------------------------------------------------------------*) IMPORT Display, Input, Files, Viewers, Texts, TextFrames, Oberon, PopupElems, HandlerElems, Bitmaps; CONST left =2; middle = 1; right = 0; cancel = {left, middle, right}; pixel = LONG(10000); CR = 0DX; grey2 = 13; Elem* = POINTER TO ElemDesc; ElemDesc* = RECORD (PopupElems.ElemDesc) END; Node = POINTER TO NodeDesc; NodeDesc = RECORD key: ARRAY 32 OF CHAR; pos: LONGINT; left, right: Node END; icon: Display.Pattern; SuperHandle: Display.Handler; stdDict: Texts.Text; (*search a name in this dictionary if it is not found in any local dictionary*) tree: Node; (*standard directory tree*) w: Texts.Writer; (*----- Balloon Elements -----*) PROCEDURE Handle* (e: Texts.Elem; VAR m: Texts.ElemMsg); VAR e1: Elem; buf: Texts.Buffer; f: TextFrames.Frame; v, v0: Viewers.Viewer; x, y: INTEGER; BEGIN WITH e: Elem DO WITH m: Texts.CopyMsg DO IF m.e = NIL THEN NEW(e1); m.e := e1 END; PopupElems.Handle(e, m) | m: Texts.IdentifyMsg DO m.mod := "BalloonElems"; m.proc := "Alloc" | m: TextFrames.DisplayMsg DO IF m.prepare THEN e.W := 13 * pixel; e.H := LONG(TextFrames.menuH-1) * pixel; ELSE e.name := ""; PopupElems.Handle(e, m); Display.CopyPattern(Display.white, icon, m.X0+2, m.Y0+2, Display.paint) END | m: TextFrames.TrackMsg DO Texts.Delete(e.menu, 0, e.menu.len); (*save it in recall buffer*) Texts.Write(w, " "); Texts.Append(e.menu, w.buf); PopupElems.MeasureMenu(e); Oberon.AllocateUserViewer(Oberon.Mouse.X, x, y); v0 := Viewers.This(x, y-1); PopupElems.Handle(e, m); v := Viewers.This(x, y-1); Texts.Recall(buf); IF v # v0 THEN (*v is the edit viewer*) f := v.dsc.next(TextFrames.Frame); Texts.Delete(f.text, 0, 1); Texts.Append(f.text, buf); Texts.Save(f.text, 0, f.text.len, buf) END; Texts.Delete(e.menu, 0, 1); Texts.Append(e.menu, buf) ELSE PopupElems.Handle(e, m) END END Handle; PROCEDURE Alloc*; VAR e: Elem; BEGIN NEW(e); e.handle := Handle; Texts.new := e END Alloc; PROCEDURE Insert*; VAR e: Elem; insert: TextFrames.InsertElemMsg; BEGIN NEW(e); e.handle := Handle; e.name := ""; e.small := TRUE; e.menu := TextFrames.Text(""); PopupElems.MeasureMenu(e); insert.e := e; Viewers.Broadcast(insert) END Insert; (*----- Binary Tree -----*) PROCEDURE Add (key: ARRAY OF CHAR; pos: LONGINT); VAR p, q, father: Node; n: INTEGER; BEGIN p := tree.right; father := tree; WHILE p # NIL DO father := p; IF key < p.key THEN p := p.left ELSE p := p.right END END; NEW(q); COPY(key, q.key); q.pos := pos; IF key < father.key THEN father.left := q ELSE father.right := q END END Add; PROCEDURE Balance; (*CACM Sept.86, pp. 902*) VAR p, tail, rest: Node; size, n, i: INTEGER; PROCEDURE Compress (root: Node; n: INTEGER); VAR p, son: Node; i: INTEGER; BEGIN p := root; FOR i := 1 TO n DO son := p.right; p.right := son.right; p := p.right; son.right := p.left; p.left := son END END Compress; BEGIN (*--- make vine ---*) tail := tree; rest := tail.right; size := 0; WHILE rest # NIL DO IF rest.left = NIL THEN (*move tail down one*) tail := rest; rest := rest.right; INC(size) ELSE (*rotate*) p := rest.left; rest.left := p.right; p.right := rest; rest := p; tail.right := p END END; (*--- make tree ---*) i := 1; WHILE i <= size+1 DO i := i + i END; n := i DIV 2 - 1; Compress(tree, size - n); WHILE n > 1 DO n := n DIV 2; Compress(tree, n) END Balance; (*----- Name Lookup -----*) PROCEDURE InvertRect (f: TextFrames.Frame; x, y, w, h: INTEGER); (*clips to right and bottom frame margin*) BEGIN IF x + w > f.X + f.W - f.right THEN w := f.X + f.W - f.right - x END; IF y >= f.Y + f.bot THEN Display.ReplConst(Display.white, x, y, w, h, Display.invert) END END InvertRect; PROCEDURE TrackMouse (VAR x, y: INTEGER; VAR keys, keysum: SET); BEGIN Input.Mouse(keys, x, y); keysum := keysum + keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) END TrackMouse; PROCEDURE CanBeWord (f: Display.Frame; x, y: INTEGER): BOOLEAN; VAR tf: TextFrames.Frame; r: Texts.Reader; ch: CHAR; BEGIN tf := f(TextFrames.Frame); Texts.OpenReader(r, tf.text, TextFrames.Pos(tf, x, y)); Texts.Read(r, ch); RETURN (r.elem = NIL) & (x > tf.X + tf.barW) END CanBeWord; PROCEDURE GetDict (t: Texts.Text; VAR dict: Texts.Text); VAR r: Texts.Reader; BEGIN Texts.OpenReader(r, t, 0); Texts.ReadElem(r); IF (r.elem # NIL) & (r.elem IS Elem) THEN dict := r.elem(Elem).menu ELSE dict := NIL END END GetDict; PROCEDURE WordBeg (t: Texts.Text; pos: LONGINT): LONGINT; VAR r: Texts.Reader; ch: CHAR; pos0: LONGINT; BEGIN pos0 := pos; Texts.OpenReader(r, t, pos); Texts.Read(r, ch); WHILE (CAP(ch) >= "A") & (CAP(ch) <= "Z") OR (ch >= "0") & (ch <= "9") OR (ch = ".") DO DEC(pos); IF pos < 0 THEN RETURN 0 END; Texts.OpenReader(r, t, pos); Texts.Read(r, ch) END; IF pos < pos0 THEN INC(pos) END; RETURN pos END WordBeg; PROCEDURE WordEnd (t: Texts.Text; pos: LONGINT): LONGINT; VAR r: Texts.Reader; ch: CHAR; pos0: LONGINT; BEGIN pos0 := pos; Texts.OpenReader(r, t, pos); Texts.Read(r, ch); WHILE (CAP(ch) >= "A") & (CAP(ch) <= "Z") OR (ch >= "0") & (ch <= "9") OR (ch = ".") DO INC(pos); Texts.Read(r, ch) END; IF pos = pos0 THEN INC(pos) END; RETURN pos END WordEnd; PROCEDURE GetName (ft: Texts.Text; pos: LONGINT; VAR name, fullName: ARRAY OF CHAR; VAR t: Texts.Text); VAR r: Texts.Reader; ch: CHAR; beg, end: LONGINT; i, j: INTEGER; imported: BOOLEAN; t0: Texts.Text; mod: ARRAY 32 OF CHAR; BEGIN (*--- read name*) beg := WordBeg(ft, pos); end:= WordEnd(ft, pos); i := 0; Texts.OpenReader(r, ft, beg); imported := FALSE; WHILE beg < end DO Texts.Read(r, ch); name[i] := ch; INC(i); INC(beg); IF ch = "." THEN imported := TRUE END END; name[i] := 0X; COPY(name, fullName); (*--- resolve import if necessary*) t := ft; IF imported THEN i := 0; WHILE name[i] # "." DO mod[i] := name[i]; INC(i) END; mod[i] := "."; mod[i+1] := "M"; mod[i+2] := "o"; mod[i+3] := "d"; mod[i+4] := 0X; NEW(t0); Texts.Open(t0, mod); IF t0.len > 0 THEN t := t0; j := 0; REPEAT INC(i); name[j] := name[i]; INC(j) UNTIL name[i] = 0X END END GetName; PROCEDURE PrefixName (VAR name: ARRAY OF CHAR; f: TextFrames.Frame); VAR v: Viewers.Viewer; s: Texts.Scanner; i, j: INTEGER; nm: ARRAY 64 OF CHAR; BEGIN v := Viewers.This(f.X, f.Y); Texts.OpenScanner(s, v.dsc(TextFrames.Frame).text, 0); Texts.Scan(s); COPY(s.s, nm); i := s.len-1; WHILE (i >= 0) & (nm[i] # ".") DO DEC(i) END; INC(i); j := 0; WHILE (i < 63) & (name[j] # 0X) DO nm[i] := name[j]; INC(i); INC(j) END; nm[i] := 0X; COPY(nm, name) END PrefixName; PROCEDURE MeasureLine (VAR r: Texts.Reader; VAR lw, lh, dsr: INTEGER); VAR ch: CHAR; x, y, w, h, dx: INTEGER; p: Display.Pattern; BEGIN Texts.Read(r, ch); lw := 0; lh := 0; dsr := 0; WHILE ~r.eot & (ch # CR) DO IF r.elem # NIL THEN h := SHORT(r.elem.H DIV pixel); dx := SHORT(r.elem.W DIV pixel); y := r.fnt.minY ELSE Display.GetChar(r.fnt.raster, ch, dx, x, y, w, h, p); INC(y, r.fnt.height * r.voff DIV 64); END; IF y < dsr THEN dsr := y END; IF y + h > lh THEN lh := y + h END; INC(lw, dx); Texts.Read(r, ch) END; dsr := -dsr; lh := lh + dsr; IF (ch = CR) & (lh = 0) THEN lh := 10; dsr := 0; lw := 8 END END MeasureLine; PROCEDURE Popup* (t: Texts.Text; beg, end: LONGINT); VAR r: Texts.Reader; ch: CHAR; x, y, w, h, dx, bx, by, bw, bh, lw, i, X, Y: INTEGER; b: Bitmaps.Bitmap; p: Display.Pattern; lh, dsr: ARRAY 128 OF INTEGER; e: Texts.Elem; dsp: TextFrames.DisplayMsg; keys: SET; BEGIN (*--- measure text*) Input.Mouse(keys, x, y); bw := 0; bh := 0; Texts.OpenReader(r, t, beg); i := 0; WHILE Texts.Pos(r) < end DO MeasureLine(r, lw, lh[i], dsr[i]); IF lw > bw THEN bw := lw END; bh := bh + lh[i]; INC(i) END; INC(bw, 8); IF bw > Display.Width THEN bw := Display.Width END; INC(bh, 8); IF bh > Display.Height THEN bh := Display.Height END; bx := x; IF bx + bw > Display.Width THEN bx := Display.Width - bw END; by := y + 10; IF by + bh > Display.Height THEN by := Display.Height - bh END; (*--- show text*) b := Bitmaps.New(bw, bh); Bitmaps.CopyBlock(Bitmaps.Disp, b, bx, by, bw, bh, 0, 0, 0); Display.ReplConst(Display.white, bx, by, bw, bh, Display.replace); Display.ReplConst(grey2, bx+1, by+1, bw-2, bh-2, Display.replace); X := bx + 4; Y := by + bh - 4 - lh[0] + dsr[0]; Texts.OpenReader(r, t, beg); i := 0; WHILE beg < end DO Texts.Read(r, ch); INC(beg); IF ch = CR THEN INC(i); X := bx + 4; Y := Y - dsr[i-1] - lh[i] + dsr[i] ELSIF r.elem # NIL THEN e := r.elem; y := r.fnt.minY; dsp.prepare := FALSE; dsp.fnt := r.fnt; dsp.col := r.col; dsp.pos := beg - 1; dsp.frame := NIL; dsp.X0 := X; dsp.Y0 := Y+y; dsp.elemFrame := NIL; e.handle(e, dsp); INC(X, SHORT(e.W DIV pixel)) ELSE Display.GetChar(r.fnt.raster, ch, dx, x, y, w, h, p); INC(y, r.fnt.height * r.voff DIV 64); Display.CopyPattern(Display.white, p, X+x, Y+y, Display.paint); X := X + dx END END; (*--- wait until right mouse button is released*) REPEAT Input.Mouse(keys, x, y) UNTIL ~(right IN keys); Bitmaps.CopyBlock(b, Bitmaps.Disp, 0, 0, bw, bh, bx, by, 0) END Popup; PROCEDURE GetBounds (VAR s: Texts.Scanner; t: Texts.Text; VAR beg, end: LONGINT); VAR ch: CHAR; BEGIN IF ~s.eot THEN beg := Texts.Pos(s); Texts.Read(s, ch); WHILE ~s.eot & (ch <= " ") & (ch # Texts.ElemChar) DO INC(beg); Texts.Read(s, ch) END; end := beg; WHILE ~s.eot & (ch # '"') DO INC(end); Texts.Read(s, ch) END; REPEAT DEC(end); Texts.OpenReader(s, t, end); Texts.Read(s, ch) UNTIL (ch > " ") OR (ch = Texts.ElemChar); INC(end); IF beg >= end THEN beg := -1 END ELSE beg := -1 END; END GetBounds; PROCEDURE Show (nm: ARRAY OF CHAR; dict: Texts.Text; VAR done: BOOLEAN); VAR s: Texts.Scanner; ch: CHAR; name: ARRAY 64 OF CHAR; beg, end: LONGINT; BEGIN COPY(nm, name); (*circumvent compiler bug*) Texts.OpenScanner(s, dict, 0); Texts.Scan(s); WHILE ~s.eot & ~((s.class = Texts.String) & (s.s = name)) DO Texts.Scan(s) END; GetBounds(s, dict, beg, end); IF beg >= 0 THEN Popup(dict, beg, end) END; done := beg >= 0 END Show; PROCEDURE ShowStd (name: ARRAY OF CHAR; VAR done: BOOLEAN); VAR p: Node; s: Texts.Scanner; beg, end: LONGINT; BEGIN p := tree.right; WHILE (p # NIL) & (p.key # name) DO IF name < p.key THEN p := p.left ELSE p := p.right END END; IF p # NIL THEN Texts.OpenScanner(s, stdDict, p.pos); GetBounds(s, stdDict, beg, end); Popup(stdDict, beg, end) END; done := p # NIL END ShowStd; PROCEDURE TrackWord (f: TextFrames.Frame; VAR m: Oberon.InputMsg); VAR keys: SET; new, old: TextFrames.Location; dict, t: Texts.Text; name, fullName: ARRAY 64 OF CHAR; beg, end: LONGINT; x, y: INTEGER; done: BOOLEAN; BEGIN TextFrames.LocateWord(f, x, y, old); InvertRect(f, old.x, old.y, old.dx, 2); m.keys := {}; TrackMouse(x, y, keys, m.keys); WHILE (keys # {}) & ~(left IN keys) DO TextFrames.LocateWord(f, x, y, new); IF new.pos # old.pos THEN InvertRect(f, old.x, old.y, old.dx, 2); InvertRect(f, new.x, new.y, new.dx, 2); old := new END; IF keys = {middle, right} THEN GetName(f.text, TextFrames.Pos(f, x, y), name, fullName, t); GetDict(t, dict); done := FALSE; IF dict # NIL THEN Show(name, dict, done) END; IF ~done THEN ShowStd(fullName, done) END; IF ~done THEN PrefixName(fullName, f); ShowStd(fullName, done) END; IF done THEN m.keys := cancel END END; TrackMouse(x, y, keys, m.keys) END; InvertRect(f, old.x, old.y, old.dx, 2); IF m.keys # cancel THEN EXCL (m.keys, left) END END TrackWord; PROCEDURE FrameHandler* (f: Display.Frame; VAR m: Display.FrameMsg); BEGIN WITH f: TextFrames.Frame DO WITH m: Oberon.InputMsg DO IF (m.id = Oberon.track) & (middle IN m.keys) & CanBeWord(f, m.X, m.Y) THEN TrackWord(f(TextFrames.Frame), m); IF m.keys # cancel THEN SuperHandle(f, m) END ELSE SuperHandle(f, m) END ELSE SuperHandle(f, m) END END FrameHandler; PROCEDURE LoadDictionary*; VAR s: Texts.Scanner; BEGIN NEW(tree); tree.key := ""; IF Files.Old("Balloon.Text") # NIL THEN NEW(stdDict); Texts.Open(stdDict, "Balloon.Text"); Texts.OpenScanner(s, stdDict, 0); Texts.Scan(s); WHILE ~s.eot DO IF s.class = Texts.String THEN Add(s.s, Texts.Pos(s)) END; Texts.Scan(s) END; Balance END LoadDictionary; PROCEDURE Install*; (*loads the module, installs the handler, and reads the dictionary*) END Install; PROCEDURE InitIcon; VAR line: ARRAY 9 OF SET; BEGIN line[8] := {}; line[7] := {1..6}; line[6] := {0, 7}; line[5] := {0, 7}; line[4] := {0, 7}; line[3] := {1..4, 6}; line[2] := {5, 6}; line[1] := {6, 7}; icon := Display.NewPattern(line, 8, 8); END InitIcon; BEGIN InitIcon; HandlerElems.SetHandler("BalloonElems.FrameHandler", FrameHandler, SuperHandle); LoadDictionary; Texts.OpenWriter(w) END BalloonElems.